﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using MethodWorx.Core.DomainModel;
using MethodWorx.CMS.Configuration;
using MethodWorx.CMS.Providers;
using MethodWorx.CMS.Web;
using MethodWorx.CMS.Plugins;
using MethodWorx.CMS.Content;
using MethodWorx.CMS.Requests;
using MethodWorx.Core.Serialization;
using System.IO;
using System.Xml;

namespace MethodWorx.CMS.Services
{
    public class SchemaService
    {
        private IPageTemplateRepository pageTemplates;
        private IContentTemplateRepository contentTemplates;
        private IUnitOfWorkFactory unitOfWorkFactory;

        public SchemaService(ICmsDataProvider provider)
            : this(provider.GetUnitOfWorkFactory(), 
                   provider.GetService<IPageTemplateRepository>(),
                   provider.GetService<IContentTemplateRepository>())
        {
        }

        public SchemaService(IUnitOfWorkFactory unitOfWorkFactory, IPageTemplateRepository pageTemplates, IContentTemplateRepository contentTemplates)
        {
            if (pageTemplates == null)
                throw new ArgumentNullException("pageTemplates");

            if (contentTemplates == null)
                throw new ArgumentNullException("contentTemplates");

            if (unitOfWorkFactory == null)
                throw new ArgumentNullException("unitOfWorkFactory");

            this.pageTemplates = pageTemplates;
            this.unitOfWorkFactory = unitOfWorkFactory;
            this.contentTemplates = contentTemplates;
        }

        public PageTemplate CreatePageTemplate(string name, string description, bool allowedAtRoot, PageTemplate parent, bool isAbstract)
        {
            var errors = new List<ValidationError>();

            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException(name);

            if (this.pageTemplates.FindByName(name) != null)
            {
                errors.Add(new ValidationError()
                {
                    Key = "Name",
                    Message = "Name already exists"
                });
            }

            if (!errors.Any())
            {
                //
                //  ok, lets create, because we are valid
                using (var unitOfWork = this.unitOfWorkFactory.Begin())
                {
                    PageTemplate template = this.pageTemplates.CreateEntityInstance();
                    template.AllowedAtRoot = allowedAtRoot;
                    template.Name = name;
                    template.Description = description;
                    template.Abstract = isAbstract;
                    template.Parent = parent;

                    unitOfWork.RegisterAdded(template);

                    //
                    //  commit!
                    unitOfWork.Commit();

                    return template;
                }

            }
            else
                throw new BusinessRuleException(errors);
        }


        public IEnumerable<PageTemplate> GetAllPageTemplates()
        {
            return this.pageTemplates.All();
        }

        public IEnumerable<PageTemplate> GetAbstractPageTemplates()
        {
            return this.pageTemplates.FindAbstract();
        }
        public PageTemplate GetAbstractPageTemplateByName(string name)
        {
            return this.pageTemplates.FindAbstractPageTemplateByName(name);
        }


        public IEnumerable<ContentTemplate> GetAllContentTemplates()
        {
            return this.contentTemplates.All();
        }

        public PageTemplate GetPageTemplateById(Guid id)
        {
            return this.pageTemplates[id];
        }

        public void ChildChildPageTemplateOf(PageTemplate parent, string name, string description, int? minimumRequired, int? maximumAllowed, string nameConstraint)
        {
            if (parent == null)
                throw new ArgumentNullException("parent");

            if (string.IsNullOrEmpty("name"))
                throw new ArgumentNullException("name");

            var errors = new List<ValidationError>();

            if (this.pageTemplates.FindByName(name) != null)
            {
                errors.Add(new ValidationError()
                {
                    Key = "Name",
                    Message = "Name already exists"
                });
            }


            if (!errors.Any())
            {
                //
                //  go for it
                using (var unitOfWork = this.unitOfWorkFactory.Begin())
                {
                    //  create the page template
                    var child = this.pageTemplates.CreateEntityInstance();

                    child.Name = name;
                    child.Description = description;
                    child.AllowedAtRoot = false;

                    unitOfWork.RegisterAdded(child);

                    //
                    //  update the parent
                    parent.PageTemplateConstraints.Add(
                        child, minimumRequired, maximumAllowed, nameConstraint);

                    unitOfWork.RegisterChanged(parent);

                    //  commit
                    unitOfWork.Commit();
                }
            }
            else
                throw new BusinessRuleException(errors);
        }

        public void AddChildPageTemplateOf(PageTemplate parent, PageTemplate child, int? minimumRequired, int? maximumAllowed, string nameConstraint)
        {
            if (parent == null)
                throw new ArgumentNullException("parent");

            if (child == null)
                throw new ArgumentNullException("child");

            if (string.IsNullOrEmpty("name"))
                throw new ArgumentNullException("name");

            //
            //  go for it
            using (var unitOfWork = this.unitOfWorkFactory.Begin())
            {
                //
                //  update the parent
                parent.PageTemplateConstraints.Add(
                    child, minimumRequired, maximumAllowed, nameConstraint);

                unitOfWork.RegisterChanged(parent);

                //  commit
                unitOfWork.Commit();
            }
        }

        public PageTemplate GetRootPageTemplate()
        {
            return this.pageTemplates.FindRoot();
        }

        public virtual ContentTemplate GetContentTemplateById(Guid id)
        {
            return this.contentTemplates[id];
        }

        public void AddChildContentTemplateOf(PageTemplate parent, ContentTemplate child, int? minimumRequired, int? maximumAllowed, string nameConstraint)
        {
             if (parent == null)
                throw new ArgumentNullException("parent");

            if (child == null)
                throw new ArgumentNullException("child");

            if (string.IsNullOrEmpty("name"))
                throw new ArgumentNullException("name");

            //
            //  go for it
            using (var unitOfWork = this.unitOfWorkFactory.Begin())
            {
                //
                //  update the parent
                parent.ContentTemplateConstraints.Add(
                    child, minimumRequired, maximumAllowed, nameConstraint);

                unitOfWork.RegisterChanged(parent);

                //  commit
                unitOfWork.Commit();
            }
        }

        public void ChildChildContentTemplateOf(PageTemplate parent, string name, string description, int? minimumRequired, int? maximumAllowed, string nameConstraint)
        {
            if (parent == null)
                throw new ArgumentNullException("parent");

            if (string.IsNullOrEmpty("name"))
                throw new ArgumentNullException("name");

            var errors = new List<ValidationError>();

            if (this.contentTemplates.FindByName(name) != null)
            {
                errors.Add(new ValidationError()
                {
                    Key = "Name",
                    Message = "Name already exists"
                });
            }


            if (!errors.Any())
            {
                //
                //  go for it
                using (var unitOfWork = this.unitOfWorkFactory.Begin())
                {
                    //  create the page template
                    var child = this.contentTemplates.CreateEntityInstance();

                    child.Name = name;
                    child.Description = description;

                    unitOfWork.RegisterAdded(child);

                    //
                    //  update the parent
                    parent.ContentTemplateConstraints.Add(
                        child, minimumRequired, maximumAllowed, nameConstraint);

                    unitOfWork.RegisterChanged(parent);

                    //  commit
                    unitOfWork.Commit();
                }
            }
            else
                throw new BusinessRuleException(errors);
        }

        public PageTemplate AddPageTemplateProperty(Guid id, string name, string type, ICustomPropertyDetails details)
        {
            if (details == null)
                throw new ArgumentNullException("details");

            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");

            var pageTemplate = this.pageTemplates[id];
            using (var unitOfWork = this.unitOfWorkFactory.Begin())
            {
                pageTemplate.Properties.Add(name, false, type, null, details);
                unitOfWork.RegisterChanged(pageTemplate);
                unitOfWork.Commit();
            }
            return pageTemplate;
        }


        public PageTemplate UpdatePageTemplateProperties(Guid id, IEnumerable<IPropertyDetails> propertyDetails)
        {
            if (propertyDetails == null)
                throw new ArgumentNullException("propertyDetails");

            var pageTemplate = this.pageTemplates[id];
            using (var unitOfWork = this.unitOfWorkFactory.Begin())
            {
                pageTemplate.Properties.Clear();
                foreach (IPropertyDetails details in propertyDetails)
                {
                    pageTemplate.Properties.Add(
                        details.Name,
                        details.Mandatory,
                        details.Type,
                        details.Group,
                        (ICustomPropertyDetails)details.Custom);
                }

                unitOfWork.RegisterChanged(pageTemplate);
                unitOfWork.Commit();
            }

            return pageTemplate;
        }

        public PageTemplate UpdatePageTemplateBasicDetails(Guid id, string name, string description, PageTemplate parent)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");

            var errors = new List<ValidationError>();
            var pageTemplate = this.pageTemplates[id];
            var found = this.pageTemplates.FindByName(name);
            if (found != null && found.Id != pageTemplate.Id)
            {
                errors.Add(new ValidationError()
                {
                    Key = "Name",
                    Message = "Name already exists"
                });
            }

            if (!errors.Any())
            {
                using (var unitOfWork = this.unitOfWorkFactory.Begin())
                {
                    pageTemplate.Name = name;
                    pageTemplate.Description = description;
                    pageTemplate.Parent = parent;

                    unitOfWork.RegisterChanged(pageTemplate);
                    unitOfWork.Commit();
                }
            }
            else
                throw new BusinessRuleException(errors);

            return pageTemplate;
        }

        public PageTemplate SaveTemplate(PageTemplate pageTemplate, string template)
        {
            if (pageTemplate == null)
                throw new ArgumentNullException("pageTemplate");

            using (IUnitOfWork uow = this.unitOfWorkFactory.Begin())
            {
                pageTemplate.Template = template;
                //  commit the unit of work
                uow.Commit();
            }

            return pageTemplate;
        }

        public void RemovePageTemplateProperty(PageTemplate pageTemplate, int index)
        {
            if (pageTemplate == null)
                throw new ArgumentNullException("pageTemplate");
            using (IUnitOfWork uow = this.unitOfWorkFactory.Begin())
            {
                pageTemplate.Properties.Remove(index);
                uow.RegisterChanged(pageTemplate);
                uow.Commit();
            }
        }

        public string SerializeToXml()
        {
            AggregateXmlSerializer serializer = new AggregateXmlSerializer();
            using(StringWriter sw = new StringWriter())
            {
                using(XmlTextWriter xml = new XmlTextWriter(sw))
                {
                    xml.Settings.OmitXmlDeclaration = false;
                    xml.WriteStartElement("Schema");
                    foreach(var pageTemplate in this.GetAllPageTemplates())
                    {
                        //
                        //  ok, write it out
                        serializer.Serialize(pageTemplate);
                    }
                    //  now do the content templates
                    foreach(var contentTemplate in this.GetAllContentTemplates())
                    {
                        //
                        //  ok, write it out
                        serializer.Serialize(contentTemplate);
                    }
                    xml.WriteEndElement();
                }

                return sw.GetStringBuilder().ToString();
            }
        }



        public IEnumerable<PageTemplate> GetAllPageTemplatesContaining(ContentTemplate contentTemplate)
        {
            return this.pageTemplates.FindContainingContentTemplate(contentTemplate);
        }

        public IEnumerable<PageTemplate> GetAllPageTemplatesContaining(PageTemplate pageTemplate)
        {
            return this.pageTemplates.FindContainingPageTemplate(pageTemplate);
        }

        public void DeleteContentTemplate(ContentTemplate contentTemplate)
        {
            using (var uow = this.unitOfWorkFactory.Begin())
            {
                contentTemplate.Deleted = true;
                uow.RegisterChanged(contentTemplate);
                uow.Commit();
            }
        }

        public void DeleteConstraint(PageTemplate pageTemplate, ContentTemplateConstraint constraint)
        {
            using (var uow = this.unitOfWorkFactory.Begin())
            {
                pageTemplate.ContentTemplateConstraints.Remove(constraint);
                uow.RegisterChanged(pageTemplate);
                uow.Commit();
            }
        }

        public void DeletePageTemplate(PageTemplate pageTemplate)
        {
            using (var uow = this.unitOfWorkFactory.Begin())
            {
                if (pageTemplate.Abstract)
                {
                    string pageTemplateName = "";
                    if(this.IsAbstractPageTemplateIsNotInUse(pageTemplate, out pageTemplateName))
                        throw new BusinessRuleException("Page Template cannot be deleted, it is being used as a parent page template for '" + pageTemplateName + "'");
                }
                    
                pageTemplate.Deleted = true;
                uow.RegisterChanged(pageTemplate);
                uow.Commit();
            }
        }

        private bool IsAbstractPageTemplateIsNotInUse(PageTemplate abstractPageTemplate, out string parent)
        {
            bool inUse = false;
            parent = "";

            foreach (var pageTemplate in this.pageTemplates.All())
            {
                if (pageTemplate.Parent != null && pageTemplate.Parent.Id == abstractPageTemplate.Id)
                {
                    inUse = true;
                    break;
                }
            }

            return inUse;
        }
        
        public void DeleteConstraint(PageTemplate pageTemplate, PageTemplateConstraint constraint)
        {
            using (var uow = this.unitOfWorkFactory.Begin())
            {
                pageTemplate.PageTemplateConstraints.Remove(constraint);
                uow.RegisterChanged(pageTemplate);
                uow.Commit();
            }
        }

        public PageTemplate GetPageTemplateByName(string templateName)
        {
            return this.pageTemplates.FindByName(templateName);
        }

        public ContentTemplate GetContentTemplateByName(string templateName)
        {
            return this.contentTemplates.FindByName(templateName);
        }
    }
}
